﻿using Newtonsoft.Json;
using PMS.Core.Configuration.CommonEnumConfigs;
using PMS.Core.Utils;
using PMS.Data.Entities.Settings;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace PMS.Services.Utils.BaiduAPI
{
    /// <summary>
    /// Represents the HTTP client to request BaiduWebApi service
    /// </summary>
    public partial class BaiduWebApiHttpClient
    {
        #region Fields

        private readonly HttpClient _httpClient;
        private readonly BaiduWebApiSettings _webapiSettings;
        // static string for BasePath of BaiduFanyiApi
        private const string BaiduFanyiApiBaseUrl = "http://api.fanyi.baidu.com/api/trans/vip/translate";
        private const string BaiduFanyiApiPath = "?q={0}&from={1}&to={2}&appid={3}&salt={4}&sign={5}";
        // BaiduIpAddress Webapi BasePath
        private const string BaiduIpLocationApiBaseUrl = "http://api.map.baidu.com";
        private const string BaiduIpLocationApiPath = "/location/ip";
        #endregion

        #region Ctor

        public BaiduWebApiHttpClient(BaiduWebApiSettings webapiSettings, HttpClient httpClient)
        {
            //configure client
            httpClient.Timeout = TimeSpan.FromSeconds(300);
            _httpClient = httpClient;

            _webapiSettings = webapiSettings;
        }

        #endregion

        #region Utils
        private static string UrlEncode(string str)
        {
            str = WebUtility.UrlEncode(str);
            byte[] buf = Encoding.ASCII.GetBytes(str);
            //query长度：为保证翻译质量，请将单次请求长度控制在 6000 bytes以内。（汉字约为2000个）
            if (buf.Length > 6000)
                throw new PMSException("The maximum length of the text for BaiduFanyi is 6000 per request", SystemLogLevel.Error);
            for (int i = 0; i < buf.Length; i++)
                if (buf[i] == '%')
                {
                    if (buf[i + 1] >= 'a') buf[i + 1] -= 32;
                    if (buf[i + 2] >= 'a') buf[i + 2] -= 32;
                    i += 2;
                }
            return Encoding.ASCII.GetString(buf);
        }

        #region 百度翻译官方提供的计算MD5值方法
        //官方提供的MD5计算方法可以正确返回请求结果
        //而使用HashHelper.CreateHash却报"54001	签名错误"
        //比对发现是返回加密字串为小写
        //private string EncryptString(string str)
        //{
        //    MD5 md5 = MD5.Create();
        //    // 将字符串转换成字节数组
        //    byte[] byteOld = Encoding.UTF8.GetBytes(str);
        //    // 调用加密方法
        //    byte[] byteNew = md5.ComputeHash(byteOld);
        //    // 将加密结果转换为字符串
        //    StringBuilder sb = new StringBuilder();
        //    foreach (byte b in byteNew)
        //    {
        //        // 将字节转换成16进制表示的字符串，
        //        sb.Append(b.ToString("x2"));
        //    }
        //    // 返回加密的字符串
        //    return sb.ToString();
        //}
        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// Get BaiduFanyiAPI result
        /// 参数 from, to所指定的语言编码参见 http://api.fanyi.baidu.com/doc/21 常见语种列表
        /// </summary>
        /// <param name="querys">待翻译的文本列表</param>
        /// <param name="from">返回用户指定的语言，或者自动检测出的语种（源语言设为 auto 时）</param>
        /// <param name="to">返回用户指定的目标语言</param>
        /// <returns>the translate result from BaiduFanyiAPI service</returns>
        public virtual async Task<IList<string>> GetBaiduFanyiResultAsync(string[] querys, string from = "auto", string to = "en")
        {
            if ((querys?.Length ?? 0) == 0)
                return null;
            if (!BaiduFanyiEnabled) 
                return null;
            string query = string.Join(';', querys.Where(w => !string.IsNullOrWhiteSpace(w)));
            var httpRequest = new BaiduFanyiRequest
            {
                from = from,
                to = to,
                q = query.TrimEnd(';'),
                salt = CommonHelper.GenerateRandomDigitCode(10)
            };
            /**
             * Step1. 拼接字符串1：
             * 拼接appid=2015063000000001+q=apple+salt=1435660288+密钥=12345678得到字符串1：“2015063000000001apple143566028812345678”
             * Step2. 计算签名：（对字符串1做md5加密）
             * sign=md5(2015063000000001apple143566028812345678)，得到sign=f89f9594663708c1605f3d736d01d2d4
             */
            httpRequest.sign = HashHelper.CreateHash(Encoding.UTF8.GetBytes(string.Concat(_webapiSettings.FanyiAppId, httpRequest.q, httpRequest.salt, _webapiSettings.FanyiAppSecretKey)), "MD5");
            //var sign = EncryptString(string.Concat(_fanyiSettings.AppId, httpRequest.q, httpRequest.salt, _fanyiSettings.AppSecretKey));
            //prepare URL to request
            var url = BaiduFanyiApiBaseUrl + string.Format(BaiduFanyiApiPath,
                UrlEncode(httpRequest.q),
                httpRequest.from,
                httpRequest.to,
                _webapiSettings.FanyiAppId,
                httpRequest.salt,
                httpRequest.sign.ToLower()); //MD5签名须为小写,否则报54001	签名错误

            //get response
            var responseStr = await _httpClient.GetStringAsync(url);
            BaiduFanyiResponse apiResponse = JsonConvert.DeserializeObject<BaiduFanyiResponse>(responseStr);
            if (!string.IsNullOrEmpty(apiResponse.error_code) && apiResponse.error_code != "52000") throw new PMSException($"Baidu Fanyi API Error: {apiResponse.error_msg}", SystemLogLevel.Error);
            return apiResponse.trans_result
                .First().dst.Split(';')
                .Select(s => s.ToLower())
                .ToList();
        }

        /// <summary>
        /// BaiduWebApi Get Ip Address Info via IpAddress
        /// http://lbsyun.baidu.com/index.php?title=webapi/ip-api
        /// Notice:普通IP定位服务目前不支持海外场景。
        /// </summary>
        /// <param name="loginIp"></param>
        /// <returns></returns>
        public virtual async Task<string> GetBaiduIpLocationAsync(IPAddress requestIp)
        {
            if (string.IsNullOrEmpty(requestIp.ToString())) 
                return null;
            if (!BaiduIpAddressEnabled)
                return null;

            Func<string, string, string> HttpBuildQuery = (ip, ak) =>
            {
                IDictionary<string, string> baiduIpLocationQuery = new Dictionary<string, string>()
                {
                    { "ip" , ip },
                    { "ak" , ak },
                    { "coor", "bd09ll" }
                };
                StringBuilder sb = new StringBuilder();
                foreach (var item in baiduIpLocationQuery)
                {
                    sb.Append(UrlEncode(item.Key));
                    sb.Append("=");
                    sb.Append(UrlEncode(item.Value));
                    sb.Append("&");
                }
                sb.Remove(sb.Length - 1, 1);
                baiduIpLocationQuery.Clear();
                return sb.ToString();
            };

            Func<string, string, string, string> CaculateAKSN = (ip, ak, sk) =>
            {
                return HashHelper.CreateHash(Encoding.UTF8.GetBytes(UrlEncode(BaiduIpLocationApiPath + "?" + HttpBuildQuery(ip, ak) + sk)), "MD5").ToLower();
            };
            string sign = CaculateAKSN(requestIp.ToString(), _webapiSettings.IpAddressAccessKey, _webapiSettings.IpAddressSecretKey);
            string Url = $"{BaiduIpLocationApiBaseUrl}{BaiduIpLocationApiPath}?{HttpBuildQuery(requestIp.ToString(), _webapiSettings.IpAddressAccessKey)}&sn={sign}";
            //get response
            var responseStr = await _httpClient.GetStringAsync(Url);
            BaiduIpAddressResponse ipaddress = JsonConvert.DeserializeObject<BaiduIpAddressResponse>(responseStr);
            //常见错误码： 0： 正常
            //           1： 内部错误
            //           2： Ip Illegal
            //           211: APP SN校验失败
            //           其他：参见 http://lbs.baidu.com/index.php?title=webapi/ip-api
            if (ipaddress.status != 0) 
                throw new PMSException($"Baidu Ip Address SDK Error: {ipaddress.message}", SystemLogLevel.Error);
            return ipaddress.content.address;
        }
        #endregion

        #region Properties
        public bool BaiduFanyiEnabled 
        {
            get
            {
                return !string.IsNullOrEmpty(_webapiSettings.FanyiAppId) && !string.IsNullOrEmpty(_webapiSettings.FanyiAppSecretKey);
            }
        }

        public bool BaiduIpAddressEnabled
        {
            get
            {
                return !string.IsNullOrEmpty(_webapiSettings.IpAddressAccessKey) && !string.IsNullOrEmpty(_webapiSettings.IpAddressSecretKey);
            }
        }
        #endregion
    }

}
